using System;
using System.Collections.Generic;
using System.Text;

namespace Decav.Geography
{
    /// <summary>
    /// Represents a geographical linear distance.
    /// </summary>
    [Serializable]
    public struct Distance
    {
        private Distance(decimal kilometers)
        {
            _kilometers = kilometers;
        }

        /// <summary>
        /// Distance is always held in kilometers.
        /// </summary>
        private decimal _kilometers;

        /// <summary>
        /// Gets a <see cref="Distance"/> from a kilometer based distance.
        /// </summary>
        /// <param name="kilometers">The number of kilometers in the distance.</param>
        /// <returns>A new <see cref="Distance"/> struct defining the distance.</returns>
        public static Distance FromKilometers(decimal kilometers)
        {
            return new Distance(kilometers);
        }

        /// <summary>
        /// Gets a <see cref="Distance"/> from a mile based distance.
        /// </summary>
        /// <param name="kilometers">The number of miles in the distance.</param>
        /// <returns>A new <see cref="Distance"/> struct defining the distance.</returns>
        public static Distance FromMiles(decimal miles)
        {
            return new Distance(miles * KilometersInMile);
        }

        /// <summary>
        /// Gets a <see cref="Distance"/> from a foot based distance.
        /// </summary>
        /// <param name="kilometers">The number of feet in the distance.</param>
        /// <returns>A new <see cref="Distance"/> struct defining the distance.</returns>
        public static Distance FromFeet(decimal feet)
        {
            return FromMiles(feet / FeetInMile);
        }

        /// <summary>
        /// Gets the number of kilometers in the distance.
        /// </summary>
        /// <returns>The distance in kilometers</returns>
        public decimal ToKilometers()
        {
            return _kilometers;
        }

        /// <summary>
        /// Gets the number of feet in the distance.
        /// </summary>
        /// <returns>The distance in feet.</returns>
        public decimal ToFeet()
        {
            return ToMiles() * FeetInMile;
        }

        /// <summary>
        /// Gets the number of miles in the distance.
        /// </summary>
        /// <returns>The distance in miles</returns>
        public decimal ToMiles()
        {
            return _kilometers / KilometersInMile;
        }

        /// <summary>
        /// The number of feet in a mile.
        /// </summary>
        private const decimal FeetInMile = 5280;

        /// <summary>
        /// The number of kilometers in a mile.
        /// </summary>
        private const decimal KilometersInMile = 1.609344m;

        /// <summary>
        /// An empty value of the <see cref="Distance"/>.
        /// </summary>
        public static readonly Distance Empty = new Distance();

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (obj.GetType() != typeof(Distance))
                return false;

            return _kilometers == ((Distance)obj)._kilometers;
        }

        public override int GetHashCode()
        {
            return _kilometers.GetHashCode();
        }

        /// <summary>
        /// Gets the kilometer value of the <see cref="Distance"/> as a string.
        /// </summary>
        /// <returns>The kilometer valued distance.</returns>
        public override string ToString()
        {
            return string.Format("{0} km", _kilometers);
        }
    }
}
